package com.cy.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

public class HttpRequest {
    private Socket socket;
    private InputStream in;

    //请求行相关信息
    private String method;
    private String url;
    private String protocol;
    //请求参数解析
    private String requestURI;
    private String queryString;
    private Map<String ,String > parameter  = new HashMap<>();

    //消息头相关信息
    private Map<String ,String > headers = new HashMap<>();

    //消息正文相关信息
    private byte[] content;

    public HttpRequest(Socket socket) throws EmptyRequestException {
        try {
            this.socket = socket;
            this.in = socket.getInputStream();
            //解析请求行
            parseRequestLine();
            //解析消息头
            parseHeaders();
            //解析消息正文
            parseContent();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void parseRequestLine() throws EmptyRequestException{
        System.out.println("正在解析请求行");
        try {
            String line = readLine();
            if("".equals(line)) {
                throw new EmptyRequestException("空请求");
            }
            System.out.println("请求行: "+line);
            String[] split = line.split("\\s");
            this.method = split[0];
            this.url = split[1];
            this.protocol = split[2];
            paseUrl();
            System.out.println("method: "+method);
            System.out.println("url: "+url);
            System.out.println("protocol: "+protocol);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("解析请求行完毕");
    }

    private void paseUrl() {
        System.out.println("进一步解析url...");
        /*
         * url可能存在两种情况:
         * 带参数或不带参数。是否带参数可以通过查看当前
         * url中是否含有"?"来得知。
         *
         * 如果该url不含有参数，那么直接将url赋值给属性
         * requestURI即可。
         * 若含有参数，那么就按照"?"将url拆分为两部分
         * 第一部分("?"左侧内容)赋值给requestURI属性，
         * 第二部分("?"右侧内容)赋值给queryString属性
         * 然后还要对参数部分进行进一步解析:
         * 因为参数部分的格式是固定的:
         * name=value&name=value&.....
         * 所以我们可以将参数部分首先按照"&"拆分为若干段，
         * 每一段的内容应当为一个"name=value",然后我们将
         * 每一个参数再按照"="拆分为参数名与参数值，分别
         * 将他们当做key与value存入到属性parameter中完成
         * 解析参数的工作
         */
        if (url.indexOf("?")!=-1) {
            String[] data = url.split("\\?");
            requestURI = data[0];
            if(data.length>1){
               queryString = data[1];
                try {
                    System.out.println("解码前queryString:"+queryString);
                    queryString = URLDecoder.decode(queryString, "UTF-8");
                    System.out.println("解码后queryString:"+queryString);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
               parseParameters(queryString);
            }
        }else {
            requestURI = url;
        }
        System.out.println("requestURI:"+requestURI);
        System.out.println("queryString:"+queryString);
        System.out.println("parameter:"+parameter);
        System.out.println("解析url完毕!");
    }

    private void parseHeaders() {
        System.out.println("正在解析消息头");
        try {
            while (true) {
                String line = readLine();
                if("".equals(line)){
                    break;
                }
                String[] split = line.split(": ");
                headers.put(split[0],split[1]);
            }
            System.out.println("headers："+headers);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("解析消息头完毕");
    }

    private void parseContent() throws IOException {
        System.out.println("正在解析消息正文");
        System.out.println("解析消息正文...");
        /*
         * 首先判断该请求是否含有消息正文，判断依据是看
         * 当前请求的消息头中是否含有Content-Length
         */
        if(headers.containsKey("Content-Length")) {
            /*
             * 获取消息正文的长度，并实际读取对应的字节量
             */
            int len = Integer.parseInt(
                    headers.get("Content-Length")
            );
            byte[] data = new byte[len];
            //将正文内容读取出来
            in.read(data);
            //设置到消息正文对应属性上
            content = data;
            //通过消息头获取该消息正文的类型
            String type = headers.get("Content-Type");
            /*
             * 根据类型判定当前消息正文内容
             */
            //判断是否为表单提交的数据
            if("application/x-www-form-urlencoded".equals(type)) {
                /*
                 * 那么该正文是一行字符串，内容就是原GET请求提交表单
                 * 时url中"?"右侧内容
                 */
                String line = new String(content,"ISO8859-1");
                System.out.println("正文内容:"+line);
                //解码
                try {
                    line = URLDecoder.decode(line, "UTF-8");
                } catch (Exception e) {
                    e.printStackTrace();
                }
                //进一步解析参数
                parseParameters(line);
            }
        }
        System.out.println("解析消息正文完毕");
    }

    private void parseParameters(String line) {
        String[] data = line.split("&");
        for(String paraLine : data) {
            //按照"="将参数拆分为两部分
            String[] paraArr = paraLine.split("=");
            //判断该参数是否有值
            if(paraArr.length>1) {
                parameter.put(paraArr[0], paraArr[1]);
            }else {
                parameter.put(paraArr[0], null);
            }
        }
    }

    public String getParameter(String key){
        return parameter.get(key);
    }

    public String getRequestURI() {
        return requestURI;
    }

    public void setRequestURI(String requestURI) {
        this.requestURI = requestURI;
    }

    public String getQueryString() {
        return queryString;
    }

    public void setQueryString(String queryString) {
        this.queryString = queryString;
    }

    public String  getHeader(String key){
        return headers.get(key);
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getProtocol() {
        return protocol;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    private String readLine() throws IOException {
        StringBuilder builder = new StringBuilder();
        int d = -1;
        char c1 = 'a',c2 = 'a';
        while ((d=in.read())!=-1){
            c2 = (char) d;
            if(c1==13&&c2==10){
                break;
            }
            builder.append(c2);
            c1 = c2;
        }
        return builder.toString().trim();
    }
}
